﻿/*
  Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
  The contents of this file are subject to the Mozilla Public License Version
  1.1 (the "License"); you may not use this file except in compliance with
  the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/
  
  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  for the specific language governing rights and limitations under the
  License.
  
  The Original Code is [maashaack framework].
  
  The Initial Developers of the Original Code are
  Zwetan Kjukov <zwetan@gmail.com> and Marc Alcaraz <ekameleon@gmail.com>.
  Portions created by the Initial Developers are Copyright (C) 2006-2014
  the Initial Developers. All Rights Reserved.
  
  Contributor(s):
  
  Alternatively, the contents of this file may be used under the terms of
  either the GNU General Public License Version 2 or later (the "GPL"), or
  the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  in which case the provisions of the GPL or the LGPL are applicable instead
  of those above. If you wish to allow use of your version of this file only
  under the terms of either the GPL or the LGPL, and not to allow others to
  use your version of this file under the terms of the MPL, indicate your
  decision by deleting the provisions above and replace them with the notice
  and other provisions required by the LGPL or the GPL. If you do not delete
  the provisions above, a recipient may use your version of this file under
  the terms of any one of the MPL, the GPL or the LGPL.
*/
package molecule.render.starling.display
{
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    import starling.core.RenderSupport;
    import starling.display.DisplayObject;
    import starling.display.Image;
    import starling.display.QuadBatch;
    import starling.display.Sprite;
    import starling.events.Event;
    import starling.textures.Texture;
    import starling.textures.TextureSmoothing;
    import starling.utils.MatrixUtil;
    
    /**
     * Tiles a sprite with a specific texture to fill this specified bounds.
     */
    public class TiledSprite extends Sprite
    {
        /**
         * Creates a new TiledSprite instance.
         * @param texture The texture to tile.
         * @param scale The amount to scale the texture. Useful for DPI changes.
         */
        public function TiledSprite( texture:Texture , scale:Number = 1 )
        {
            _hitArea      = new Rectangle();
            _textureScale = scale ;
            
            this.texture = texture ;
            
            setSize( _originalImageWidth  * _textureScale ,  _originalImageHeight * _textureScale ) ;
            
            _batch = new QuadBatch();
            _batch.touchable = false;
            
            addChild( _batch );
            
            addEventListener( Event.FLATTEN , _flatten ) ;
        }
        
        /**
         * The color value to pass to the tiled images.
         */
        public function get color():uint
        {
            return _color;
        }
        
        /**
         * @private
         */
        public function set color(value:uint):void
        {
            if( _color == value )
            {
                return ;
            }
            _color = value;
            _propertiesChanged = true;
        }
        
        /**
         * The height of the image.
         */
        public override function get height():Number
        {
            return _height;
        }
        
        /**
         * @private
         */
        override public function set height(value:Number):void
        {
            if( _height == value )
            {
                return;
            }
            _height = _hitArea.height = value;
            _layoutChanged = true;
        }
        
        
        /**
         * The smoothing value to pass to the tiled images.
         * @see starling.textures.TextureSmoothing
         */
        public function get smoothing():String
        {
            return _smoothing;
        }
        
        /**
         * @private
         */
        public function set smoothing( value:String ):void 
        {
            if( TextureSmoothing.isValid( value ) )
            {
                _smoothing = value ;
            }
            else
            {
                throw new ArgumentError(this + " Invalid smoothing mode: " + value ) ;
            }
            _propertiesChanged = true;
        }
        
        /**
         * The texture to tile.
         */
        public function get texture():Texture
        {
            return _texture;
        }
        
        /**
         * @private
         */
        public function set texture( value:Texture ):void 
        { 
            if(value == null)
            {
                throw new ArgumentError("Texture cannot be null");
            }
            if(_texture == value)
            {
                return;
            }
            _texture = value;
            if(!_image)
            {
                _image = new Image(value);
                _image.touchable = false;
            }
            else
            {
                _image.texture = value;
                _image.readjustSize();
            }
            _originalImageWidth  = value.frame.width;
            _originalImageHeight = value.frame.height;
            _layoutChanged = true;
        }
        
        /**
         * The amount to scale the texture. Useful for DPI changes.
         */
        public function get textureScale():Number
        {
            return _textureScale;
        }
        
        /**
         * @private
         */
        public function set textureScale(value:Number):void
        {
            if(_textureScale == value)
            {
                return;
            }
            _textureScale = value;
            _layoutChanged = true;
        }
        
        /**
         * The width of the image.
         */
        public override function get width():Number
        {
            return _width;
        }
        
        /**
         * @private
         */
        public override function set width(value:Number):void
        {
            if( _width == value )
            {
                return;
            }
            _width = _hitArea.width = value;
            _layoutChanged = true;
        }
        
        /**
         * Returns a rectangle that completely encloses the object as it appears in another coordinate system.
         * @return a rectangle that completely encloses the object as it appears in another coordinate system.
         */
        public override function getBounds( targetSpace:DisplayObject , resultRect:Rectangle=null ):Rectangle
        {
            if(!resultRect)
            {
                resultRect = new Rectangle();
            }
            
            var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE;
            var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE;
            
            if (targetSpace == this) // optimization
            {
                minX = _hitArea.x;
                minY = _hitArea.y;
                maxX = _hitArea.x + _hitArea.width;
                maxY = _hitArea.y + _hitArea.height;
            }
            else
            {
                getTransformationMatrix(targetSpace, _MATRIX);
                
                MatrixUtil.transformCoords(_MATRIX, _hitArea.x, _hitArea.y, _ORIGIN);
                minX = minX < _ORIGIN.x ? minX : _ORIGIN.x;
                maxX = maxX > _ORIGIN.x ? maxX : _ORIGIN.x;
                minY = minY < _ORIGIN.y ? minY : _ORIGIN.y;
                maxY = maxY > _ORIGIN.y ? maxY : _ORIGIN.y;
                
                MatrixUtil.transformCoords(_MATRIX, _hitArea.x, _hitArea.y + _hitArea.height, _ORIGIN);
                minX = minX < _ORIGIN.x ? minX : _ORIGIN.x;
                maxX = maxX > _ORIGIN.x ? maxX : _ORIGIN.x;
                minY = minY < _ORIGIN.y ? minY : _ORIGIN.y;
                maxY = maxY > _ORIGIN.y ? maxY : _ORIGIN.y;
                
                MatrixUtil.transformCoords(_MATRIX, _hitArea.x + _hitArea.width, _hitArea.y, _ORIGIN);
                minX = minX < _ORIGIN.x ? minX : _ORIGIN.x;
                maxX = maxX > _ORIGIN.x ? maxX : _ORIGIN.x;
                minY = minY < _ORIGIN.y ? minY : _ORIGIN.y;
                maxY = maxY > _ORIGIN.y ? maxY : _ORIGIN.y;
                
                MatrixUtil.transformCoords(_MATRIX, _hitArea.x + _hitArea.width, _hitArea.y + _hitArea.height, _ORIGIN);
                minX = minX < _ORIGIN.x ? minX : _ORIGIN.x;
                maxX = maxX > _ORIGIN.x ? maxX : _ORIGIN.x;
                minY = minY < _ORIGIN.y ? minY : _ORIGIN.y;
                maxY = maxY > _ORIGIN.y ? maxY : _ORIGIN.y;
            }
            
            resultRect.x = minX;
            resultRect.y = minY;
            resultRect.width  = maxX - minX;
            resultRect.height = maxY - minY;
            
            return resultRect;
        }
        
        /**
         * Optimizes the sprite for optimal rendering performance.
         */
        public override function flatten():void
        {
            validate();
            super.flatten();
        }
        
        /**
         * Returns the object that is found topmost beneath a point in local coordinates, or nil if the test fails.
         * @return the object that is found topmost beneath a point in local coordinates, or nil if the test fails.
         */
        override public function hitTest( localPoint:Point, forTouch:Boolean=false ):DisplayObject
        {
            if( forTouch && (!visible || !touchable) )
            {
                return null ;
            }
            return _hitArea.containsPoint(localPoint) ? this : null;
        }
        
        /**
         * @private
         */
        public override function render( support:RenderSupport , parentAlpha:Number ):void
        {
            validate();
            super.render( support , parentAlpha ) ;
        }
        
        
        /**
         * Set both the width and height in one call.
         */
        public function setSize( w:Number , h:Number ):void
        {
            if( _width == w && _height == h )
            {
                return;
            }
            _width  = _hitArea.width  = w ;
            _height = _hitArea.height = h ;
            _layoutChanged = true;
        }
        
        /////////////
        
        private var _batch:QuadBatch;
        
        private var _color:uint = 0xFFFFFF;
        
        private var _height:Number ;
        
        private var _hitArea:Rectangle;
        
        private var _image:Image;
        
        private var _layoutChanged:Boolean = true ;
        
        private static const _MATRIX:Matrix = new Matrix();
        
        private static const _ORIGIN:Point = new Point();
        
        private var _originalImageWidth:Number = 0 ;
        
        private var _originalImageHeight:Number = 0 ;
        
        private var _propertiesChanged:Boolean = true;
        
        private var _smoothing:String = TextureSmoothing.BILINEAR;
        
        private var _texture:Texture;
        
        private var _textureScale:Number = 1;
        
        private var _width:Number ;
        
        /**
         * @private
         */
        protected function validate():void
        {
            if( _propertiesChanged )
            {
                _image.smoothing = _smoothing;
                _image.color     = _color;
            }
            if( _propertiesChanged || _layoutChanged )
            {
                _batch.reset();
                _image.scaleX = _image.scaleY = _textureScale;
                
                const scaledTextureWidth:Number = _originalImageWidth * _textureScale;
                const scaledTextureHeight:Number = _originalImageHeight * _textureScale;
                
                const xImageCount:int = Math.ceil(_width / scaledTextureWidth);
                const yImageCount:int = Math.ceil(_height / scaledTextureHeight);
                
                const imageCount:int = xImageCount * yImageCount;
                
                var imageWidth:Number ;
                var imageHeight:Number ;
                
                var xCoord:Number ;
                var yCoord:Number ;
                
                var xPosition:Number = 0;
                var yPosition:Number = 0;
                
                var nextXPosition:Number = xPosition + scaledTextureWidth;
                var nextYPosition:Number = yPosition + scaledTextureHeight;
                
                for( var i:int ; i < imageCount ; i++ )
                {
                    _image.x = xPosition;
                    _image.y = yPosition;
                    
                    imageWidth  = (nextXPosition >= _width) ? (_width - xPosition) : scaledTextureWidth;
                    imageHeight = (nextYPosition >= _height) ? (_height - yPosition) : scaledTextureHeight;
                    
                    _image.width = imageWidth;
                    _image.height = imageHeight;
                    
                    xCoord = imageWidth / scaledTextureWidth;
                    yCoord = imageHeight / scaledTextureHeight;
                    
                    _ORIGIN.x = xCoord;
                    _ORIGIN.y = 0;
                    
                    _image.setTexCoords(1, _ORIGIN);
                    
                    _ORIGIN.y = yCoord;
                    _image.setTexCoords(3, _ORIGIN);
                    
                    _ORIGIN.x = 0;
                    _image.setTexCoords(2, _ORIGIN);
                    
                    _batch.addImage(_image);
                    
                    if( nextXPosition >= _width )
                    {
                        xPosition = 0;
                        nextXPosition = scaledTextureWidth;
                        yPosition = nextYPosition;
                        nextYPosition += scaledTextureHeight;
                    }
                    else
                    {
                        xPosition = nextXPosition;
                        nextXPosition += scaledTextureWidth;
                    }
                }
            }
            _layoutChanged = false;
            _propertiesChanged = false;
        }
        
        /**
         * @private
         */
        private function _flatten( event:Event ):void
        {
            validate() ;
        }
    }
}